#include "library.h"

#include <iostream>
#include <sstream>
#include <utility>

#include "sqlite3pp/sqlite3pp.h"

using namespace pita;

namespace fs = boost::filesystem;
namespace s3 = sqlite3pp;

namespace
{

	std::string quote(const std::string& sz)
	{
		return std::string(1, '"') + sz + '"';
	}

	/* Determines if path needs an update in the database. It needs
	   an update if either a) there is no info in the db for that
	   path or b) newTime is greater than the stored modtime for
	   the path */
	bool getUpdateNeeded(s3::database& db, 
						 const fs::path& path,
						 std::time_t newTime)
	{
		std::ostringstream oss;
		oss << "SELECT modtime FROM files "
			<< "WHERE path = " << quote(path.string());

		s3::query query(db, oss.str().c_str());
		s3::query::iterator itr = query.begin();
		if (itr == query.end()) return true;
		
		std::time_t oldTime = itr->get<int64_t>(0);
		return newTime > oldTime;
	}

	fs::path relativePath(const fs::path& root,
						  const fs::path& path)
	{
		std::string rootsz(root.string());
		std::string pathsz(path.string());
		return std::string(
			pathsz.substr(
				rootsz.size(), 
				pathsz.size() - rootsz.size()));
	}

}

Library::iterator::iterator()
{}

Library::iterator::iterator(s3::database& db)
{
	m_query.reset(new s3::query(db, "SELECT * FROM files"));
	m_iterator = m_query->begin();
}

void Library::iterator::operator++()
{
	++m_iterator;
}

bool Library::iterator::operator!=(const iterator& itr) const
{
	return !(*this == itr);
}

bool Library::iterator::operator==(const iterator& itr) const
{
	return atEnd() && itr.atEnd();
}

std::string Library::iterator::path() const
{
	return m_iterator->get<std::string>(1);
}

std::time_t Library::iterator::modtime() const
{
	return m_iterator->get<int64_t>(2);
}

bool Library::iterator::atEnd() const
{
	return (!m_query) || (m_iterator == m_query->end());
}

Library::Library(const fs::path& root,
				 const fs::path& dbpath) :
	root_ (root),
	db_ (dbpath.string().c_str())
{
	initDB();
}

void Library::update()
{
	checkDirectory(root_, root_);
}

Library::iterator Library::begin() const
{
	return iterator(const_cast<s3::database&>(db_));
}

Library::iterator Library::end() const
{
	return iterator();
}

void Library::checkDirectory(const fs::path& root,
							 const fs::path& dir)
{
	using namespace fs;

	assert(exists(dir));

	directory_iterator end_itr;
	for (directory_iterator itr(dir);
		  itr != end_itr;
		  ++itr )
	{
		if (!exists(itr->path()))
			continue;
		else if (is_directory(itr->status()))
		{
			checkDirectory(root, itr->path());
		}
		else if (is_regular(itr->path()))
		{
			handleFile(root, itr->path());
		}
	}
}

void Library::handleFile(const fs::path& root,
						 const fs::path& file)
{
	// TODO: Insert check that this is a file type we can deal with...or not? Perhaps just let the source objects deal with that?

	assert(is_regular(file) && exists(file));
	assert(std::distance(file.begin(), file.end()) > 1);

	std::time_t modtime = last_write_time(file);

	if (!::getUpdateNeeded(db_, file, modtime)) return;

	std::ostringstream oss;
	oss << "INSERT OR REPLACE INTO files (path, modtime) VALUES ("
		<< quote(relativePath(root, file).string())
		<< ", " << modtime 
		<< ")";
	s3::command cmd(db_, oss.str().c_str());

	// This should work but produces bad results...
	// cmd.bind(1, ::relativePath(root, file).string().c_str());
	// cmd.bind(2, static_cast<int64_t>(modtime));
	cmd.execute();
}

void Library::initDB()
{
	db_.execute(
		"CREATE TABLE IF NOT EXISTS files ("
		"  id INTEGER PRIMARY KEY AUTOINCREMENT"
		", path STRING UNIQUE NOT NULL"
		", modtime INTEGER NOT NULL"
		")"
		);

	db_.execute(
		"CREATE TABLE IF NOT EXISTS metadata ("
		"  id INTEGER PRIMARY KEY AUTOINCREMENT"
		", file_id INTEGER NOT NULL"
		", artist STRING"
		", album STRING"
		", track INTEGER"
		", year INTEGER"
		")"
		);
}
